<!doctype html>



  


<html class="theme-next mist use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>






<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />




  
  
  
  

  
    
    
  

  

  

  

  

  
    
    
    <link href="//fonts.googleapis.com/css?family=Lato:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext" rel="stylesheet" type="text/css">
  






<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.1" rel="stylesheet" type="text/css" />


  <meta name="keywords" content="Linux,JavaEE,小程序," />





  <link rel="alternate" href="/atom.xml" title="Bingo's Blog" type="application/atom+xml" />




  <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico?v=5.1.1" />






<meta name="description" content="前言前阵子遇到一个需求，微信小程序的某一个界面，需要屏幕截图，然后分享出去。但小程序官方没有开放可以截取超过屏幕的完整界面的 API，也就是只能截取当前屏幕可以看到的内容。而我们需要截长图，所以现有的 API 不能满足需求，只能服务端想办法生成图片。我偶然发现了 phantomjs，可以由服务端去做类似爬虫的操作来保存图片。">
<meta name="keywords" content="Linux,JavaEE,小程序">
<meta property="og:type" content="article">
<meta property="og:title" content="phantomjs实现服务端屏幕截图">
<meta property="og:url" content="https://bingozb.github.io/51.html">
<meta property="og:site_name" content="Bingo's Blog">
<meta property="og:description" content="前言前阵子遇到一个需求，微信小程序的某一个界面，需要屏幕截图，然后分享出去。但小程序官方没有开放可以截取超过屏幕的完整界面的 API，也就是只能截取当前屏幕可以看到的内容。而我们需要截长图，所以现有的 API 不能满足需求，只能服务端想办法生成图片。我偶然发现了 phantomjs，可以由服务端去做类似爬虫的操作来保存图片。">
<meta property="og:image" content="https://bingozb.github.io/images/blog/51/phantomjs.png">
<meta property="og:image" content="https://bingozb.github.io/images/blog/51/card.png">
<meta property="og:updated_time" content="2017-09-21T18:35:20.000Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="phantomjs实现服务端屏幕截图">
<meta name="twitter:description" content="前言前阵子遇到一个需求，微信小程序的某一个界面，需要屏幕截图，然后分享出去。但小程序官方没有开放可以截取超过屏幕的完整界面的 API，也就是只能截取当前屏幕可以看到的内容。而我们需要截长图，所以现有的 API 不能满足需求，只能服务端想办法生成图片。我偶然发现了 phantomjs，可以由服务端去做类似爬虫的操作来保存图片。">
<meta name="twitter:image" content="https://bingozb.github.io/images/blog/51/phantomjs.png">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Mist',
    sidebar: {"position":"left","display":"post","offset":12,"offset_float":0,"b2t":false,"scrollpercent":false},
    fancybox: true,
    motion: true,
    duoshuo: {
      userId: 'undefined',
      author: '博主'
    },
    algolia: {
      applicationID: 'JE30EVH7NI',
      apiKey: '862bb3deb9a3166ed99de0be6527f59c',
      indexName: 'blog',
      hits: {"per_page":10},
      labels: {"input_placeholder":"输入关键词进行搜索","hits_empty":"找不到关于「 ${query} 」的文章","hits_stats":"共找到 ${hits} 篇文章，花了 ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://bingozb.github.io/51.html"/>





  <title>phantomjs实现服务端屏幕截图 | Bingo's Blog</title>
</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  





  <script type="text/javascript">
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?c8a59b9753bce3ec58f81c89e2acce96";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
  </script>










  
  
    
  

  <div class="container sidebar-position-left page-post-detail ">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">Bingo's Blog</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">Stay Hungry, Stay Foolish.</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
            
            归档
          </a>
        </li>
      
        
        <li class="menu-item menu-item-about">
          <a href="/about" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br />
            
            关于
          </a>
        </li>
      

      
        <li class="menu-item menu-item-search">
          
            <a href="javascript:;" class="popup-trigger">
          
            
              <i class="menu-item-icon fa fa-search fa-fw"></i> <br />
            
            搜索
          </a>
        </li>
      
    </ul>
  

  
    <div class="site-search">
      
  <div class="popup search-popup local-search-popup">
  <div class="local-search-header clearfix">
    <span class="search-icon">
      <i class="fa fa-search"></i>
    </span>
    <span class="popup-btn-close">
      <i class="fa fa-times-circle"></i>
    </span>
    <div class="local-search-input-wrapper">
      <input autocapitalize="off" autocomplete="off" autocorrect="off"
             placeholder="搜索..." spellcheck="false"
             type="text" id="local-search-input">
    </div>
  </div>
  <div id="local-search-result"></div>
</div>



    </div>
  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://bingozb.github.io/51.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Bingo">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Bingo's Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                phantomjs实现服务端屏幕截图
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-09-18T16:09:57+08:00">
                2017-09-18
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/战斗笔记/" itemprop="url" rel="index">
                    <span itemprop="name">战斗笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
              <span class="post-meta-divider">|</span>
              <span class="post-meta-item-icon">
                <i class="fa fa-comment-o"></i>
              </span>
              
                <a href="/51.html#SOHUCS" itemprop="discussionUrl">
                  <span id="changyan_count_unit" class="post-comments-count hc-comment-count" data-xid="51.html" itemprop="commentsCount"></span>
                </a>
              
            
          

          
          
             <span id="/51.html" class="leancloud_visitors" data-flag-title="phantomjs实现服务端屏幕截图">
               <span class="post-meta-divider">|</span>
               <span class="post-meta-item-icon">
                 <i class="fa fa-eye"></i>
               </span>
               
                 <span class="post-meta-item-text">阅读次数 </span>
               
                 <span class="leancloud-visitors-count"></span>
             </span>
          

          

          

          

        </div>
      </header>
    

    <div class="post-body" itemprop="articleBody">

      
      

      
        <p><img src="/images/blog/51/phantomjs.png" alt="phantomjs"></p>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>前阵子遇到一个需求，微信小程序的某一个界面，需要屏幕截图，然后分享出去。但小程序官方没有开放可以截取超过屏幕的完整界面的 API，也就是只能截取当前屏幕可以看到的内容。而我们需要截长图，所以现有的 API 不能满足需求，只能服务端想办法生成图片。我偶然发现了 phantomjs，可以由服务端去做类似爬虫的操作来保存图片。</p>
<a id="more"></a>
<h2 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h2><p>想到一种解决方案，就是做一个 H5 的页面，服务端用一个基于 webkit 内核的无界面浏览器 phantomjs，去访问 H5 页面地址，渲染，生成图片，并保存到服务器上，从而实现截图功能。</p>
<h2 id="phantomjs"><a href="#phantomjs" class="headerlink" title="phantomjs"></a>phantomjs</h2><p><a href="http://phantomjs.org/" target="_blank" rel="external">官网 http://phantomjs.org/</a></p>
<p>phantomjs 是 一个基于 webkit 内核的无头浏览器，没有 UI 界面。它就是一个浏览器，只是内部的点击、翻页等人为相关操作需要程序设计实现。</p>
<p>提供了 javascript API 接口，可以通过 js 直接与 webkit 内核交互，在此之上可以结合 Java 语言等，通过 Java 调用 js 等相关操作，从而解决了以前 c/c++ 才能比较好的基于 webkit 开发优质采集器的限制。</p>
<p>提供了 windows、linux、mac 等不同 OS 的安装使用包，也就是说可以在不同平台上，二次开发采集项目（爬虫）或是自动项目测试等工作。</p>
<h3 id="常用内置对象"><a href="#常用内置对象" class="headerlink" title="常用内置对象"></a>常用内置对象</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 获得系统操作对象，包括命令行参数、phantomjs系统设置等信息</span></div><div class="line"><span class="keyword">var</span> system = <span class="built_in">require</span>(<span class="string">'system'</span>);</div><div class="line"></div><div class="line"><span class="comment">// 获取操作dom或web网页的对象，通过它可以打开网页、接收网页内容、request、response参数，其为最核心对象。</span></div><div class="line"><span class="keyword">var</span> page = <span class="built_in">require</span>(<span class="string">'webpage'</span>);</div><div class="line"></div><div class="line"><span class="comment">// 获取文件系统对象，通过它可以操作操作系统的文件操作，包括read、write、move、copy、delete等。</span></div><div class="line"><span class="keyword">var</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</div></pre></td></tr></table></figure>
<h3 id="常用API"><a href="#常用API" class="headerlink" title="常用API"></a>常用API</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 通过page对象通过url链接打开页面，加载完成后回调</span></div><div class="line">page.open(url, <span class="function"><span class="keyword">function</span> (<span class="params">status</span>) </span>&#123;&#125;</div><div class="line"></div><div class="line"><span class="comment">// 当page.open调用时，会首先执行该函数，在此可以预置一些参数或函数，用于后边的回调函数中</span></div><div class="line">page.onLoadStarted = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;&#125;</div><div class="line"></div><div class="line"><span class="comment">// page的所要加载的资源在加载过程中，失败回调处理</span></div><div class="line">page.onResourceError = <span class="function"><span class="keyword">function</span>(<span class="params">resourceError</span>) </span>&#123;&#125;</div><div class="line"></div><div class="line"><span class="comment">// page的所要加载的资源在发起请求时，可以回调该函数</span></div><div class="line">page.onResourceRequested = <span class="function"><span class="keyword">function</span>(<span class="params">requestData, networkRequest</span>) </span>&#123;&#125;</div><div class="line"></div><div class="line"><span class="comment">// page的所要加载的资源在加载过程中，每加载一个相关资源，都会在此先做出响应，它相当于http头部分，核心回调对象为response，可以获取本次请求的cookies、userAgent等</span></div><div class="line">page.onResourceReceived = <span class="function"><span class="keyword">function</span>(<span class="params">response</span>) </span>&#123;&#125;</div><div class="line"></div><div class="line"><span class="comment">// 打印一些输出信息到控制台</span></div><div class="line">page.onConsoleMessage = <span class="function"><span class="keyword">function</span> (<span class="params">msg</span>) </span>&#123;&#125;</div><div class="line"></div><div class="line"><span class="comment">// alert也是无法直接弹出的，但可以回调alert的内容</span></div><div class="line">page.onAlert = <span class="function"><span class="keyword">function</span>(<span class="params">msg</span>) </span>&#123;&#125;</div><div class="line"></div><div class="line"><span class="comment">// 当page.open时，http请求（不包括所引起的其它的加载资源）出现了异常，如404、no route to web site等，都会在此回调显示。</span></div><div class="line">page.onError = <span class="function"><span class="keyword">function</span>(<span class="params">msg, trace</span>) </span>&#123;&#125;</div><div class="line"></div><div class="line"><span class="comment">// 当page.open打开的url，或者是在打开过程中进行了跳转，可以在这个函数中回调。</span></div><div class="line">page.onUrlChanged = <span class="function"><span class="keyword">function</span>(<span class="params">targetUrl</span>) </span>&#123;&#125;</div><div class="line"></div><div class="line"><span class="comment">// 当page.open的目标URL被真正打开后，会在调用open的回调函数前调用该函数，在此可以进行内部的翻页等操作</span></div><div class="line">page.onLoadFinished = <span class="function"><span class="keyword">function</span>(<span class="params">status</span>) </span>&#123;&#125;</div><div class="line"></div><div class="line"><span class="comment">// 在所加载的web page内部执行该函数，像翻页、点击、滑动等，均可在此中执行</span></div><div class="line">page.evaluate(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;&#125;);</div><div class="line"></div><div class="line"><span class="comment">// 将当前page的现状渲染成图片，输出到指定的文件中去。</span></div><div class="line">page.render(<span class="string">""</span>);</div></pre></td></tr></table></figure>
<h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><h3 id="下载-phantomjs"><a href="#下载-phantomjs" class="headerlink" title="下载 phantomjs"></a>下载 phantomjs</h3><p>可以直接去<a href="http://phantomjs.org/download.html" target="_blank" rel="external">官网</a>, 找到对应服务器操作系统的压缩包，然后下载。</p>
<figure class="highlight vim"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$ wget http<span class="variable">s:</span>//bitbucket.org/ariya/phantomjs/downloads/phantomjs-<span class="number">2.1</span>.<span class="number">1</span>-linux-x86_64.tar.bz2</div></pre></td></tr></table></figure>
<p>但我在顺丰的服务器里下载 phantomjs 官网提供的压缩包，下载速度连 1KB/s 都没有。<br>或者也下载源码，进行编译（编译真的超级慢）。于是我只能先下载到本地，然后上传到七牛云 CDN 加速的存储空间。</p>
<p><strong>下载 &amp; 解压</strong></p>
<figure class="highlight vim"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">$ wget http<span class="variable">s:</span>//cdn.bingo.ren/phantomjs-<span class="number">2.1</span>.<span class="number">1</span>-linux-x86_64.tar.bz2</div><div class="line">$ tar -jxvf phantomjs-<span class="number">2.1</span>.<span class="number">1</span>-linux-x86_64.tar.bz2</div></pre></td></tr></table></figure>
<p>phantomjs 的可执行脚本在根目录下的 bin 目录下，测试一下是否可用</p>
<figure class="highlight vim"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">$ phantomjs-<span class="number">2.1</span>.<span class="number">1</span>-linux-x86_64/bin/phantomjs -v</div><div class="line">--&gt;<span class="number">2.1</span>.<span class="number">1</span></div></pre></td></tr></table></figure>
<p>除此之外，还需要一个 js 脚本，来当 phantomjs 的参数被调用。</p>
<h3 id="request-js"><a href="#request-js" class="headerlink" title="request.js"></a>request.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> page = <span class="built_in">require</span>(<span class="string">'webpage'</span>).create();</div><div class="line"><span class="keyword">var</span> system = <span class="built_in">require</span>(<span class="string">'system'</span>);</div><div class="line"></div><div class="line"><span class="keyword">var</span> address = system.args[<span class="number">1</span>];   <span class="comment">// 截图页面地址</span></div><div class="line"><span class="keyword">var</span> output = system.args[<span class="number">2</span>];    <span class="comment">// 保存图片名</span></div><div class="line"></div><div class="line">page.viewportSize = &#123; <span class="attr">width</span>: <span class="number">414</span>*<span class="number">2</span>, <span class="attr">height</span>: <span class="number">736</span>*<span class="number">2</span> &#125;; <span class="comment">// 页面初始高度</span></div><div class="line"></div><div class="line">page.open(address, <span class="function"><span class="keyword">function</span> (<span class="params">status</span>) </span>&#123; <span class="comment">// 打开页面</span></div><div class="line">        <span class="keyword">if</span> (status === <span class="string">"success"</span>) &#123; <span class="comment">// 加载完成</span></div><div class="line">                <span class="comment">// 通过在JS获取页面的渲染高度</span></div><div class="line">                <span class="keyword">var</span> rect = page.evaluate(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</div><div class="line">                  <span class="keyword">return</span> <span class="built_in">document</span>.getElementsByTagName(<span class="string">'html'</span>)[<span class="number">0</span>].getBoundingClientRect();</div><div class="line">                &#125;);</div><div class="line">                <span class="comment">// 按照实际页面的高度，设定渲染的宽高</span></div><div class="line">                page.clipRect = &#123;</div><div class="line">                  <span class="attr">top</span>:    rect.top,</div><div class="line">                  <span class="attr">left</span>:   rect.left,</div><div class="line">                  <span class="attr">width</span>:  rect.width,</div><div class="line">                  <span class="attr">height</span>: rect.height</div><div class="line">                &#125;;</div><div class="line">                <span class="comment">// 预留一定的渲染时间</span></div><div class="line">                <span class="built_in">window</span>.setTimeout(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</div><div class="line">                  page.render(output);</div><div class="line">                  <span class="comment">// var base64 = page.renderBase64('JPEG');</span></div><div class="line">                  <span class="comment">// console.log(base64);</span></div><div class="line">                  page.close();</div><div class="line">                  <span class="built_in">console</span>.log(<span class="string">'success'</span>);</div><div class="line">                  phantom.exit();</div><div class="line">                &#125;, <span class="number">1000</span>);</div><div class="line">        &#125;</div><div class="line">&#125;);</div></pre></td></tr></table></figure>
<p>渲染图片的 API 有两个：</p>
<ul>
<li><code>page.render(&#39;IMG_NAME&#39;)</code> : 渲染并保存图片到指定的路径</li>
<li><code>page.renderBase64(&#39;JPEG&#39;)</code> : 渲染并转换图片为指定格式的 Base64 编码</li>
</ul>
<p>切记，代码最后一定要加入 <code>phantom.exit()</code> 方法确保 phantomjs 退出。</p>
<h3 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h3><figure class="highlight vim"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$ /bin/phantomjs request.js http<span class="variable">s:</span>//www.baidu.<span class="keyword">com</span> images/baidu.png</div></pre></td></tr></table></figure>
<p>看到终端控制台输出了<code>success</code>，则保存图片成功，实现了 html 页面的截屏。</p>
<h2 id="JavaEE-调度"><a href="#JavaEE-调度" class="headerlink" title="JavaEE 调度"></a>JavaEE 调度</h2><p>小程序调用 JavaEE 接口，应用在接口的实现类中去调 phantomJS，渲染生成图片，上传到七牛云，并把七牛云图片地址返回给前端，最终实现截屏。</p>
<h3 id="声明常量"><a href="#声明常量" class="headerlink" title="声明常量"></a>声明常量</h3><p>首先定义字符串常量，包括要访问并保存图片的 h5 页面地址，phantomJS 脚本路径，js 路径，保存图片的路径等等，根据项目的需求和服务器的配置自行修改。</p>
<h4 id="DKConstant-java"><a href="#DKConstant-java" class="headerlink" title="DKConstant.java"></a>DKConstant.java</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"> * 蛋壳域名</div><div class="line"> */</div><div class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DK_DOMIN = <span class="string">"https://api-wxc.sf-rush.com/"</span>;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"> * phantomJS 配置</div><div class="line"> */</div><div class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DK_PHANTOMJS_PATH = <span class="string">"/data/wwwroot/sftc.dankal.cn/phantomjs/"</span>;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"> * phantomJS 脚本路径</div><div class="line"> */</div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String DK_PHANTOMJS_SHELLPATH = DK_PHANTOMJS_PATH + <span class="string">"bin/phantomjs"</span>;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"> * phantomJS js路径</div><div class="line"> */</div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String DK_PHANTOMJS_JSPATH = DK_PHANTOMJS_PATH + <span class="string">"request.js"</span>;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"> * phantomJS 截图保存路径</div><div class="line"> */</div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String DK_PHANTOMJS_OUTPUTPATH = DK_PHANTOMJS_PATH + <span class="string">"images/"</span>;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"> * phantomJS 页面地址</div><div class="line"> */</div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String DK_PHANTOMJS_WEB_URL = DK_DOMIN + <span class="string">"web/index.html?order_id="</span>;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"> * phantomJS 图片地址</div><div class="line"> */</div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String DK_PHANTOMJS_IMAGES = DK_DOMIN + <span class="string">"phantomjs/images/"</span>;</div></pre></td></tr></table></figure>
<h3 id="封装网页截屏工具类"><a href="#封装网页截屏工具类" class="headerlink" title="封装网页截屏工具类"></a>封装网页截屏工具类</h3><p>主要用到了 Runtime 去调用系统的</p>
<p><code>Runtime.getRuntime()</code>可以取得当前 JVM 的运行时环境，是 Java 中唯一一个可以获取到运行时环境的方法。Runtime 可以用来调用外部命令，可以自己写一个 bat 或 shell 脚本，然后用 <code>exec(cmd)</code> 来调用它。</p>
<p>我们就用它去调用 phantomJS。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"> * HTML页面截屏工具类</div><div class="line"> */</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HtmlScreenShotUtil</span> </span>&#123;</div><div class="line"></div><div class="line">    <span class="comment">/**</span></div><div class="line">     * 网页截屏，并保存图片</div><div class="line">     *</div><div class="line">     * <span class="doctag">@param</span> url    页面地址</div><div class="line">     * <span class="doctag">@param</span> output 保存图片名(不带后缀)</div><div class="line">     */</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">screenShot</span><span class="params">(String url, String output)</span> </span>&#123;</div><div class="line"></div><div class="line">        String outPutPath = DK_PHANTOMJS_OUTPUTPATH + output;</div><div class="line"></div><div class="line">        Runtime rt = Runtime.getRuntime();</div><div class="line">        StringBuilder sb = <span class="keyword">new</span> StringBuilder();</div><div class="line">        <span class="keyword">try</span> &#123;</div><div class="line">            String cmd = DK_PHANTOMJS_SHELLPATH + <span class="string">" "</span> + DK_PHANTOMJS_JSPATH + <span class="string">" "</span> + url + <span class="string">" "</span> + outPutPath;</div><div class="line">            Process process = rt.exec(cmd);</div><div class="line">            InputStream is = process.getInputStream();</div><div class="line">            BufferedReader br = <span class="keyword">new</span> BufferedReader(<span class="keyword">new</span> InputStreamReader(is));</div><div class="line">            String tmp = <span class="string">""</span>;</div><div class="line">            <span class="keyword">try</span> &#123;</div><div class="line">                <span class="keyword">while</span> ((tmp = br.readLine()) != <span class="keyword">null</span>) &#123;</div><div class="line">                    sb.append(tmp);</div><div class="line">                &#125;</div><div class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</div><div class="line">                e.printStackTrace();</div><div class="line">            &#125;</div><div class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</div><div class="line">            e.printStackTrace();</div><div class="line">        &#125;</div><div class="line"></div><div class="line">        <span class="keyword">return</span> sb.toString();</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h3 id="接口调用"><a href="#接口调用" class="headerlink" title="接口调用"></a>接口调用</h3><h4 id="请求"><a href="#请求" class="headerlink" title="请求"></a>请求</h4><p>POST /order/share/screenShot</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">&#123;</div><div class="line">	<span class="attr">"order_id"</span>: <span class="number">3631</span>,</div><div class="line">	<span class="attr">"name"</span>: <span class="string">"Bingo💤"</span></div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h4 id="服务端业务逻辑"><a href="#服务端业务逻辑" class="headerlink" title="服务端业务逻辑"></a>服务端业务逻辑</h4><p>SpringMVC 相关的略过，主要贴一下在业务逻辑层中对工具类的调用。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"> * 订单分享屏幕截图</div><div class="line"> */</div><div class="line"><span class="function"><span class="keyword">public</span> APIResponse <span class="title">screenShot</span><span class="params">(APIRequest request)</span> </span>&#123;</div><div class="line"></div><div class="line">    JSONObject requestObject = JSONObject.fromObject(request.getRequestParam());</div><div class="line">    </div><div class="line">    <span class="keyword">if</span> (!requestObject.containsKey(<span class="string">"order_id"</span>))</div><div class="line">        <span class="keyword">return</span> APIUtil.paramErrorResponse(<span class="string">"order_id不能为空"</span>);</div><div class="line"></div><div class="line">    String url = DK_PHANTOMJS_WEB_URL + requestObject.getInt(<span class="string">"order_id"</span>);</div><div class="line">    String imgName = System.currentTimeMillis() + <span class="string">".jpg"</span>;</div><div class="line">    </div><div class="line">    <span class="comment">// 保存图片</span></div><div class="line">    String result = HtmlScreenShotUtil.screenShot(url, imgName);</div><div class="line">    <span class="comment">//  imgName = DK_PHANTOMJS_IMAGES + imgName; // 可直接访问的路径</span></div><div class="line"></div><div class="line">    JSONObject resultObject = <span class="keyword">new</span> JSONObject();</div><div class="line">    <span class="keyword">if</span> (result.endsWith(<span class="string">"success"</span>)) &#123;</div><div class="line">        <span class="comment">// 上传七牛云，返回七牛图片路径</span></div><div class="line">        String imgSrc = qiniuService.uploadImageWithBase64(result.replace(<span class="string">"success"</span>, <span class="string">""</span>));</div><div class="line">        resultObject.put(<span class="string">"img"</span>, imgSrc);</div><div class="line">        <span class="keyword">return</span> APIUtil.getResponse(SUCCESS, resultObject);</div><div class="line">    &#125; <span class="keyword">else</span> &#123;</div><div class="line">        resultObject.put(<span class="string">"error"</span>, result);</div><div class="line">        <span class="keyword">return</span> APIUtil.submitErrorResponse(<span class="string">"生成图片失败"</span>, resultObject);</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>我这里使用的渲染的方法是 <code>page.renderBase64(&#39;JPEG&#39;)</code> 而不是 <code>page.render(output)</code>, 在控制台会先输出图片的 base64 编码，然后再输出 <code>success</code>，也就是以<code>success</code>结尾。</p>
<p>所以我的处理是，判断返回的结果是不是以<code>success</code>结尾，如果是，则说明是图片渲染成功，把结果字符串中的<code>success</code>替换为空字符串，那么剩下的就是图片的 base64 编码了。</p>
<p>接着调用自己封装的七牛云上传的服务类的方法，参数为图片的 base64 编码，把图片上传到七牛云，最后把返回的七牛云图片地址返回给前端，一整套操作就结束了。</p>
<p>为什么这里不直接用 <code>imgName</code> 呢？（看注释掉的那一行，由于「服务器上保存图片的地址」与「项目的根路径」是在同级目录，所以可以直接通过相同的域名访问到，imgName 其实是保存在服务器上的图片的 HTTP URL。）</p>
<p>原因有两点：</p>
<ul>
<li>因为有上传七牛云的需求，直接通过 base64 上传图片更快，更简单。</li>
<li>我遇到了一个问题，一直保存不了图片。</li>
</ul>
<p>这两个点都是坑，真的踩了很久，踩得很深，非常有必要记录下来。</p>
<h4 id="接口响应"><a href="#接口响应" class="headerlink" title="接口响应"></a>接口响应</h4><figure class="highlight json"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">&#123;</div><div class="line">    <span class="attr">"state"</span>: <span class="number">200</span>, </div><div class="line">    <span class="attr">"message"</span>: <span class="string">"success"</span>, </div><div class="line">    <span class="attr">"result"</span>: &#123;</div><div class="line">        <span class="attr">"img"</span>: <span class="string">"https://sf.dankal.cn/share/15059086604928607cc30c67c.jpg"</span></div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h4 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h4><!--![card.png](/images/blog/51/card.png)-->
<p><img src="/images/blog/51/card.png" width="414" height="736"></p>
<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><h3 id="渲染-Base64-的方式，上传图片失败"><a href="#渲染-Base64-的方式，上传图片失败" class="headerlink" title="渲染 Base64 的方式，上传图片失败"></a>渲染 Base64 的方式，上传图片失败</h3><p>使用 <code>page.renderBase64(&#39;JPEG&#39;);</code> 渲染并转换图片为指定格式的 Base64 编码，phantomjs 的这个 API 没有问题的。但是我遇到了一个情况，就是当 request.js 中的宽高设得很大时，会有问题。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">page.viewportSize = &#123; <span class="attr">width</span>: <span class="number">414</span>*<span class="number">2</span>, <span class="attr">height</span>: <span class="number">736</span>*<span class="number">2</span> &#125;;</div></pre></td></tr></table></figure>
<p>为了渲染的图片的分辨率足够高，保证图片足够清晰，我一开始直接用 Retina 屏 iPhone 7 的比例「4.7英寸，414 x 746（4倍）」，也就是 <code>{width: 414*4, height: 736*4}</code>。</p>
<p>结果发现获取到的 base64 编码非常长，超过了字符串的最大长度 65355。那么获取到的 base64 编码在转为 Java 字符串的时候会抛异常，就算不抛异常，也是不完整的。上传肯定会失败的，就算上传成功，图片也是无法显示或者显示不完整的。</p>
<h4 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h4><p>理论上是解决不了的，但为什么我这里还是使用这种方案呢？因为经过调试，我发现2倍的4.7英寸的分辨率，刚刚合适，清晰度还行，Base64 编码的长度也只有几千。</p>
<p>因为我们的界面是有固定的模板的，生成的每一张图片几乎都只有文字内容是不同的，所差无几，很稳定。 Base64 编码的长度不会超过 65355，所以可以规避这种方案的缺陷。</p>
<p><strong>感悟：真正合适的方案应该是经过比较、评估、权衡而得出来的，而不是理论上的。</strong></p>
<h3 id="保存图片的方式，保存不了图片"><a href="#保存图片的方式，保存不了图片" class="headerlink" title="保存图片的方式，保存不了图片"></a>保存图片的方式，保存不了图片</h3><p>问题是这样的，在完成各种常规操作后，在我本机的 macOS 一点问题都没有。<br>同样的操作，部署到 centOS 后，shell 直接操作 phantomjs 没有问题。<br>单独写一个 Java 测试类来调用 phantomjs，在终端中用 javac 编译 java 文件，然后再 java 执行 class 文件，也没有问题。<br>但是在 centOS 部署了 JavaEE 后，前端访问接口，服务端再去调用 phantomjs，就出问题了。</p>
<p>通过 debug，发现确实有调用了 phantomjs，在 phantomjs 的 js 中 console.log 的输出也完全正常，可以看到已经爬到网页源代码，一切似乎都很正常，唯独图片没有被保存到服务器上。</p>
<p>tomcat 没有指定用户和权限，相关的目录权限都是 755。</p>
<h4 id="解决-1"><a href="#解决-1" class="headerlink" title="解决"></a>解决</h4><p>在万能的创软俱乐部中，我提出了我的问题，万能的超哥就提出了建议：</p>
<blockquote>
<p>基本是权限问题了<br>ps指令看看真正的运行帐号是什么<br>也许只是你以为是root</p>
</blockquote>
<p>我的第一反应就是，<code>sudo ...</code> 也就是在 cmd 前加 sudo。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">rt.exec(<span class="string">"sudo "</span> + cmd);</div></pre></td></tr></table></figure>
<p>然而并没有作用，好吧，耍小聪明还是没用的，还是看一下 ps 吧。先在我的 macOS 上执行：</p>
<figure class="highlight vim"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">$ <span class="keyword">ps</span> -ef | <span class="keyword">grep</span> tomcat</div><div class="line">  <span class="number">501</span> <span class="number">91736</span> <span class="number">85028</span>   <span class="number">0</span>  <span class="number">5</span>:<span class="number">25</span>PM ??         <span class="number">0</span>:<span class="number">51.52</span> /Library/Java/JavaVirtualMachines/jdk1.<span class="number">8.0</span>_144.jdk/Contents/Home/bin/java -Djava.util.logging.config.<span class="keyword">file</span>=/Users/bingo/Library/Caches/IntelliJIdea2017.<span class="number">1</span>/tomcat/Unnamed_javaweb_ddj_2/<span class="keyword">conf</span>/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Dcom.<span class="keyword">sun</span>.management.jmxremote= -Dcom.<span class="keyword">sun</span>.management.jmxremote.port=<span class="number">1099</span> -Dcom.<span class="keyword">sun</span>.management.jmxremote.ssl=false -Dcom.<span class="keyword">sun</span>.management.jmxremote.authenticate=false -Djava.rmi.server.<span class="built_in">hostname</span>=<span class="number">127.0</span>.<span class="number">0.1</span> -Djdk.tls.ephemeralDHKeySize=<span class="number">2048</span> -Djava.endorsed.dirs=/usr/local/apache-tomcat-<span class="number">8.0</span>.<span class="number">36</span>/endorsed -classpath /usr/local/apache-tomcat-<span class="number">8.0</span>.<span class="number">36</span>/bin/bootstrap.jar:/usr/local/apache-tomcat-<span class="number">8.0</span>.<span class="number">36</span>/bin/tomcat-juli.jar -Dcatalina.base=/Users/bingo/Library/Caches/IntelliJIdea2017.<span class="number">1</span>/tomcat/Unnamed_javaweb_ddj_2 -Dcatalina.home=/usr/local/apache-tomcat-<span class="number">8.0</span>.<span class="number">36</span> -Djava.io.tmpdir=/usr/local/apache-tomcat-<span class="number">8.0</span>.<span class="number">36</span>/temp org.apache.catalina.startup.Bootstrap start</div><div class="line">  <span class="number">501</span> <span class="number">91868</span> <span class="number">91555</span>   <span class="number">0</span>  <span class="number">5</span>:<span class="number">26</span>PM ttys001    <span class="number">0</span>:<span class="number">00.00</span> <span class="keyword">grep</span> --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn tomcat</div></pre></td></tr></table></figure>
<p>可以看到用户是 501，就是当前我登录的用户，权限没有问题。接下来看一下服务器上的：</p>
<figure class="highlight vim"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">$ <span class="keyword">ps</span> -ef | <span class="keyword">grep</span> tomcat</div><div class="line">root       <span class="number">306</span> <span class="number">32747</span>  <span class="number">0</span> <span class="number">17</span>:<span class="number">26</span> <span class="keyword">pts</span>/<span class="number">0</span>    <span class="number">00</span>:<span class="number">00</span>:<span class="number">00</span> <span class="keyword">grep</span> --color tomcat</div><div class="line">www      <span class="number">32111</span>     <span class="number">1</span>  <span class="number">2</span> <span class="number">16</span>:<span class="number">33</span> ?        <span class="number">00</span>:<span class="number">01</span>:<span class="number">08</span> /usr/java/jdk1.<span class="number">8.0</span>_121/bin/java -Djava.util.logging.config.<span class="keyword">file</span>=/usr/local/tomcat/<span class="keyword">conf</span>/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.security.egd=<span class="keyword">file</span>:/dev/./urandom -server -Xms256m -Xmx938m -Dfile.encoding=UTF-<span class="number">8</span> -Djdk.tls.ephemeralDHKeySize=<span class="number">2048</span> -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Djava.library.path=/usr/local/apr/lib -Djava.endorsed.dirs=/usr/local/tomcat/endorsed -classpath /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap start</div></pre></td></tr></table></figure>
<p><strong>www</strong> ! 好吧，看来真的是权限问题。给 www 用户分配图片保存的目录的写入权限即可。</p>
<figure class="highlight vim"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">$ su www</div><div class="line">$ chmod <span class="keyword">u</span>+<span class="keyword">x</span> /path/<span class="keyword">to</span>/save/img</div></pre></td></tr></table></figure>
<h3 id="解决中文乱码"><a href="#解决中文乱码" class="headerlink" title="解决中文乱码"></a>解决中文乱码</h3><p>用 phantomjs 去截取中文页面的网站可能会出现乱码的情况，也就是截图中中文的位置全是方框。</p>
<p>解决办法：<strong>安装字体</strong>。</p>
<figure class="highlight vim"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"># centOS</div><div class="line">$ yum install bitmap-fonts bitmap-fonts-cjk</div><div class="line"># ubuntu</div><div class="line">$ sudo apt-<span class="built_in">get</span> install xfonts-wqy</div></pre></td></tr></table></figure>
<h3 id="解决字体大小不一"><a href="#解决字体大小不一" class="headerlink" title="解决字体大小不一"></a>解决字体大小不一</h3><p>执行上面命令安装完后，再去截图中文的页面就不会出现一堆的方框了，但是字体存在粗细不一致的问题，而且字体很丑，跟我在 macOS 下的苹方字体差别太大了。</p>
<p>解决办法：安装<strong>微软雅黑</strong>字体，建立字体索引，更新字体缓存。</p>
<p>微软雅黑字体 <code>msyh.ttf</code> 有点难找，可以从 windows 下拷贝过去，也可以在网上下载。由于我是 macOS，只能去网上找了，为了方便以后系统迁移以及多个环境需要再次下载，我已经传到 CDN 七牛云上了，下载地址：<a href="https://cdn.bingo.ren/msyh.ttf" target="_blank" rel="external">https://cdn.bingo.ren/msyh.ttf</a></p>
<figure class="highlight vim"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"># centOS</div><div class="line">$ yum -<span class="keyword">y</span> install mkfontscale fontconfig</div><div class="line"># unbuntu</div><div class="line">$ apt-<span class="built_in">get</span> -<span class="keyword">y</span> install fontconfig xfonts-utils</div><div class="line"></div><div class="line">$ <span class="built_in">mkdir</span> /usr/share/fonts/win/</div><div class="line"># 下载微软雅黑字体</div><div class="line">$ wget http<span class="variable">s:</span>//cdn.bingo.ren/msyh.ttf -O /usr/share/fonts/win/msyh.ttf</div><div class="line"># 建立字体索引，更新字体缓存</div><div class="line">$ <span class="keyword">cd</span> /usr/share/fonts/win/</div><div class="line">$ mkfontscale</div><div class="line">$ mkfontdir</div><div class="line">$ fc-cache</div></pre></td></tr></table></figure>
<h3 id="解决-Emoji-表情乱码"><a href="#解决-Emoji-表情乱码" class="headerlink" title="解决 Emoji 表情乱码"></a>解决 Emoji 表情乱码</h3><p>安装完微软雅黑字体后，再去截图，发现中文字体的还原度很高了，大小粗细等问题都没有了。但是，还有一个问题，就是当中文中包含 Emoji 表情，也就是 Unicode 编码的时候，又乱码了，全都是方框。</p>
<p>解决办法：安装支持 Unicode 编码的字体。</p>
<figure class="highlight vim"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">$ <span class="keyword">cd</span> /usr/share/fonts/win/</div><div class="line">$ wget http<span class="variable">s:</span>//cdn.bingo.ren/Segoe-UI-Symbol.ttf</div></pre></td></tr></table></figure>
<p>然后再次建立字体索引，更新字体缓存</p>
<figure class="highlight vim"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">$ mkfontscale</div><div class="line">$ mkfontdir</div><div class="line">$ fc-cache</div></pre></td></tr></table></figure>
<p>然而，虽然不会出现方框了，但是黑白的。暂时解决不了，翻遍了字体都没找到彩色的 emoji 字体，也许是我理解的概念不对？emoji 的显示似乎不是应该字体支持，而是浏览器支持，而 phantomjs 却是无界面的浏览器，所以无法渲染出理想的 emoji 表情？但是我在 macOS 下是可以截成功的，难道还跟系统的架构有关系？我尝试把 macOS 的字体全部导入 centOS，也无效，待深入研究，再看能否解决。</p>
<h2 id="后话"><a href="#后话" class="headerlink" title="后话"></a>后话</h2><p>没想到服务端生成图片会遇到那么多的坑，这里我只列举了主要的注意点，其实还有很多细节的问题会卡住整套流程，比如 phantomjs 截图宽高不对，需要按照实际页面的高度，设定渲染的宽高；又比如无法截长图，需要前端控制 CSS，不能让 <code>html</code> 的 <code>height: 100%</code> 等等。</p>
<p>尽管遇到了很多坑，做了很多尝试，花了我很多时间和精力去解决问题，最后还有黑白 emoji 的问题没有解决。但我一点也不感到意外，也不会后悔。因为在解决问题的过程中，我在不断开阔思维，不断扩充知识面。随便公司给我的时间不多，但我仍然会为了最后一个问题，去研究 emoji，研究 Unicode 编码，以及它在不同平台的不同浏览器的显示差异及原理等。</p>

      
    </div>

    <div>
      
        

      
    </div>

    <div>
      
        

      
    </div>

    <div>
      
        
  <ul class="post-copyright">
    <li class="post-copyright-author">
      <strong>本文作者：</strong>
      Bingo
    </li>
    <li class="post-copyright-link">
      <strong>本文链接：</strong>
      <a href="https://bingozb.github.io/51.html" title="phantomjs实现服务端屏幕截图">https://bingozb.github.io/51.html</a>
    </li>
    <li class="post-copyright-license">
      <strong>版权声明： </strong>
      本博客所有文章除特别声明外，均采用 MIT 许可协议。转载请注明出处！
    </li>
  </ul>


      
    </div>

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tags/Linux/" rel="tag"># Linux</a>
          
            <a href="/tags/JavaEE/" rel="tag"># JavaEE</a>
          
            <a href="/tags/小程序/" rel="tag"># 小程序</a>
          
        </div>
      

      
        
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/52.html" rel="next" title="树莓派入门">
                <i class="fa fa-chevron-left"></i> 树莓派入门
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/49.html" rel="prev" title="JavaEE解决jdbc连接超时问题">
                JavaEE解决jdbc连接超时问题 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          
  <div class="comments" id="comments">
    
      <div id="SOHUCS"></div>
    
  </div>


        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap" >
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
          <img class="site-author-image" itemprop="image"
               src="/images/avatar.jpg"
               alt="Bingo" />
          <p class="site-author-name" itemprop="name">Bingo</p>
           
              <p class="site-description motion-element" itemprop="description">He who hesitates is lost.</p>
          
        </div>
        <nav class="site-state motion-element">

          
            <div class="site-state-item site-state-posts">
              <a href="/archives">
                <span class="site-state-item-count">55</span>
                <span class="site-state-item-name">日志</span>
              </a>
            </div>
          

          
            
            
            <div class="site-state-item site-state-categories">
              <a href="/categories/index.html">
                <span class="site-state-item-count">4</span>
                <span class="site-state-item-name">分类</span>
              </a>
            </div>
          

          
            
            
            <div class="site-state-item site-state-tags">
              <a href="/tags/index.html">
                <span class="site-state-item-count">22</span>
                <span class="site-state-item-name">标签</span>
              </a>
            </div>
          

        </nav>

        
          <div class="feed-link motion-element">
            <a href="/atom.xml" rel="alternate">
              <i class="fa fa-rss"></i>
              RSS
            </a>
          </div>
        

        <div class="links-of-author motion-element">
          
            
              <span class="links-of-author-item">
                <a href="https://github.com/bingozb" target="_blank" title="GitHub">
                  
                    <i class="fa fa-fw fa-github"></i>
                  
                  GitHub
                </a>
              </span>
            
              <span class="links-of-author-item">
                <a href="https://coding.net/u/bingozb" target="_blank" title="Coding">
                  
                    <i class="fa fa-fw fa-git"></i>
                  
                  Coding
                </a>
              </span>
            
              <span class="links-of-author-item">
                <a href="https://twitter.com/bingo_v587" target="_blank" title="Twitter">
                  
                    <i class="fa fa-fw fa-twitter"></i>
                  
                  Twitter
                </a>
              </span>
            
              <span class="links-of-author-item">
                <a href="https://www.facebook.com/profile.php?id=100013406670597" target="_blank" title="Facebook">
                  
                    <i class="fa fa-fw fa-facebook"></i>
                  
                  Facebook
                </a>
              </span>
            
          
        </div>

        
        

        
        

        


      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#前言"><span class="nav-number">1.</span> <span class="nav-text">前言</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#思路"><span class="nav-number">2.</span> <span class="nav-text">思路</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#phantomjs"><span class="nav-number">3.</span> <span class="nav-text">phantomjs</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#常用内置对象"><span class="nav-number">3.1.</span> <span class="nav-text">常用内置对象</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#常用API"><span class="nav-number">3.2.</span> <span class="nav-text">常用API</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#实现"><span class="nav-number">4.</span> <span class="nav-text">实现</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#下载-phantomjs"><span class="nav-number">4.1.</span> <span class="nav-text">下载 phantomjs</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#request-js"><span class="nav-number">4.2.</span> <span class="nav-text">request.js</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#测试"><span class="nav-number">4.3.</span> <span class="nav-text">测试</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#JavaEE-调度"><span class="nav-number">5.</span> <span class="nav-text">JavaEE 调度</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#声明常量"><span class="nav-number">5.1.</span> <span class="nav-text">声明常量</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#DKConstant-java"><span class="nav-number">5.1.1.</span> <span class="nav-text">DKConstant.java</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#封装网页截屏工具类"><span class="nav-number">5.2.</span> <span class="nav-text">封装网页截屏工具类</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#接口调用"><span class="nav-number">5.3.</span> <span class="nav-text">接口调用</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#请求"><span class="nav-number">5.3.1.</span> <span class="nav-text">请求</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#服务端业务逻辑"><span class="nav-number">5.3.2.</span> <span class="nav-text">服务端业务逻辑</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#接口响应"><span class="nav-number">5.3.3.</span> <span class="nav-text">接口响应</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#效果"><span class="nav-number">5.3.4.</span> <span class="nav-text">效果</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#问题"><span class="nav-number">6.</span> <span class="nav-text">问题</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#渲染-Base64-的方式，上传图片失败"><span class="nav-number">6.1.</span> <span class="nav-text">渲染 Base64 的方式，上传图片失败</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#解决"><span class="nav-number">6.1.1.</span> <span class="nav-text">解决</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#保存图片的方式，保存不了图片"><span class="nav-number">6.2.</span> <span class="nav-text">保存图片的方式，保存不了图片</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#解决-1"><span class="nav-number">6.2.1.</span> <span class="nav-text">解决</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#解决中文乱码"><span class="nav-number">6.3.</span> <span class="nav-text">解决中文乱码</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#解决字体大小不一"><span class="nav-number">6.4.</span> <span class="nav-text">解决字体大小不一</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#解决-Emoji-表情乱码"><span class="nav-number">6.5.</span> <span class="nav-text">解决 Emoji 表情乱码</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#后话"><span class="nav-number">7.</span> <span class="nav-text">后话</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright" >
  
  Copyright © 
  <span itemprop="copyrightYear">2017
  </span>
  <span class="author" itemprop="copyrightHolder">Bingo
  </span>
  All rights reserved.
</div>
        

        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>

  
  <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>

  
  <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>

  
  <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>

  
  <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>

  
  <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.1"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.1"></script>



  
  

  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.1"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.1"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.1"></script>



  


  




	





  





  





  




  
    <script type="text/javascript">
    (function(){
      var appid = 'cytcdzpa3';
      var conf = 'c0c6117021d1cea9f11629100efd8446';
      var width = window.innerWidth || document.documentElement.clientWidth;
      if (width < 960) {
      window.document.write('<script id="changyan_mobile_js" charset="utf-8" type="text/javascript" src="https://changyan.sohu.com/upload/mobile/wap-js/changyan_mobile.js?client_id=' + appid + '&conf=' + conf + '"><\/script>'); } else { var loadJs=function(d,a){var c=document.getElementsByTagName("head")[0]||document.head||document.documentElement;var b=document.createElement("script");b.setAttribute("type","text/javascript");b.setAttribute("charset","UTF-8");b.setAttribute("src",d);if(typeof a==="function"){if(window.attachEvent){b.onreadystatechange=function(){var e=b.readyState;if(e==="loaded"||e==="complete"){b.onreadystatechange=null;a()}}}else{b.onload=a}}c.appendChild(b)};loadJs("https://changyan.sohu.com/upload/changyan.js",function(){
        window.changyan.api.config({appid:appid,conf:conf})});
      }
    })();
    </script>
    <script type="text/javascript" src="https://assets.changyan.sohu.com/upload/plugins/plugins.count.js"></script>
  



  

  <script type="text/javascript">
    // Popup Window;
    var isfetched = false;
    var isXml = true;
    // Search DB path;
    var search_path = "search.xml";
    if (search_path.length === 0) {
      search_path = "search.xml";
    } else if (search_path.endsWith("json")) {
      isXml = false;
    }
    var path = "/" + search_path;
    // monitor main search box;

    var onPopupClose = function (e) {
      $('.popup').hide();
      $('#local-search-input').val('');
      $('.search-result-list').remove();
      $('#no-result').remove();
      $(".local-search-pop-overlay").remove();
      $('body').css('overflow', '');
    }

    function proceedsearch() {
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay"></div>')
        .css('overflow', 'hidden');
      $('.search-popup-overlay').click(onPopupClose);
      $('.popup').toggle();
      $('#local-search-input').focus();
    }

    // search function;
    var searchFunc = function(path, search_id, content_id) {
      'use strict';

      // start loading animation
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay">' +
          '<div id="search-loading-icon">' +
          '<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
          '</div>' +
          '</div>')
        .css('overflow', 'hidden');
      $("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');

      $.ajax({
        url: path,
        dataType: isXml ? "xml" : "json",
        async: true,
        success: function(res) {
          // get the contents from search data
          isfetched = true;
          $('.popup').detach().appendTo('.header-inner');
          var datas = isXml ? $("entry", res).map(function() {
            return {
              title: $("title", this).text(),
              content: $("content",this).text(),
              url: $("url" , this).text()
            };
          }).get() : res;
          var input = document.getElementById(search_id);
          var resultContent = document.getElementById(content_id);
          var inputEventFunction = function() {
            var searchText = input.value.trim().toLowerCase();
            var keywords = searchText.split(/[\s\-]+/);
            if (keywords.length > 1) {
              keywords.push(searchText);
            }
            var resultItems = [];
            if (searchText.length > 0) {
              // perform local searching
              datas.forEach(function(data) {
                var isMatch = false;
                var hitCount = 0;
                var searchTextCount = 0;
                var title = data.title.trim();
                var titleInLowerCase = title.toLowerCase();
                var content = data.content.trim().replace(/<[^>]+>/g,"");
                var contentInLowerCase = content.toLowerCase();
                var articleUrl = decodeURIComponent(data.url);
                var indexOfTitle = [];
                var indexOfContent = [];
                // only match articles with not empty titles
                if(title != '') {
                  keywords.forEach(function(keyword) {
                    function getIndexByWord(word, text, caseSensitive) {
                      var wordLen = word.length;
                      if (wordLen === 0) {
                        return [];
                      }
                      var startPosition = 0, position = [], index = [];
                      if (!caseSensitive) {
                        text = text.toLowerCase();
                        word = word.toLowerCase();
                      }
                      while ((position = text.indexOf(word, startPosition)) > -1) {
                        index.push({position: position, word: word});
                        startPosition = position + wordLen;
                      }
                      return index;
                    }

                    indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
                    indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
                  });
                  if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
                    isMatch = true;
                    hitCount = indexOfTitle.length + indexOfContent.length;
                  }
                }

                // show search results

                if (isMatch) {
                  // sort index by position of keyword

                  [indexOfTitle, indexOfContent].forEach(function (index) {
                    index.sort(function (itemLeft, itemRight) {
                      if (itemRight.position !== itemLeft.position) {
                        return itemRight.position - itemLeft.position;
                      } else {
                        return itemLeft.word.length - itemRight.word.length;
                      }
                    });
                  });

                  // merge hits into slices

                  function mergeIntoSlice(text, start, end, index) {
                    var item = index[index.length - 1];
                    var position = item.position;
                    var word = item.word;
                    var hits = [];
                    var searchTextCountInSlice = 0;
                    while (position + word.length <= end && index.length != 0) {
                      if (word === searchText) {
                        searchTextCountInSlice++;
                      }
                      hits.push({position: position, length: word.length});
                      var wordEnd = position + word.length;

                      // move to next position of hit

                      index.pop();
                      while (index.length != 0) {
                        item = index[index.length - 1];
                        position = item.position;
                        word = item.word;
                        if (wordEnd > position) {
                          index.pop();
                        } else {
                          break;
                        }
                      }
                    }
                    searchTextCount += searchTextCountInSlice;
                    return {
                      hits: hits,
                      start: start,
                      end: end,
                      searchTextCount: searchTextCountInSlice
                    };
                  }

                  var slicesOfTitle = [];
                  if (indexOfTitle.length != 0) {
                    slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));
                  }

                  var slicesOfContent = [];
                  while (indexOfContent.length != 0) {
                    var item = indexOfContent[indexOfContent.length - 1];
                    var position = item.position;
                    var word = item.word;
                    // cut out 100 characters
                    var start = position - 20;
                    var end = position + 80;
                    if(start < 0){
                      start = 0;
                    }
                    if (end < position + word.length) {
                      end = position + word.length;
                    }
                    if(end > content.length){
                      end = content.length;
                    }
                    slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));
                  }

                  // sort slices in content by search text's count and hits' count

                  slicesOfContent.sort(function (sliceLeft, sliceRight) {
                    if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
                      return sliceRight.searchTextCount - sliceLeft.searchTextCount;
                    } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
                      return sliceRight.hits.length - sliceLeft.hits.length;
                    } else {
                      return sliceLeft.start - sliceRight.start;
                    }
                  });

                  // select top N slices in content

                  var upperBound = parseInt('-1');
                  if (upperBound >= 0) {
                    slicesOfContent = slicesOfContent.slice(0, upperBound);
                  }

                  // highlight title and content

                  function highlightKeyword(text, slice) {
                    var result = '';
                    var prevEnd = slice.start;
                    slice.hits.forEach(function (hit) {
                      result += text.substring(prevEnd, hit.position);
                      var end = hit.position + hit.length;
                      result += '<b class="search-keyword">' + text.substring(hit.position, end) + '</b>';
                      prevEnd = end;
                    });
                    result += text.substring(prevEnd, slice.end);
                    return result;
                  }

                  var resultItem = '';

                  if (slicesOfTitle.length != 0) {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + highlightKeyword(title, slicesOfTitle[0]) + "</a>";
                  } else {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + title + "</a>";
                  }

                  slicesOfContent.forEach(function (slice) {
                    resultItem += "<a href='" + articleUrl + "'>" +
                      "<p class=\"search-result\">" + highlightKeyword(content, slice) +
                      "...</p>" + "</a>";
                  });

                  resultItem += "</li>";
                  resultItems.push({
                    item: resultItem,
                    searchTextCount: searchTextCount,
                    hitCount: hitCount,
                    id: resultItems.length
                  });
                }
              })
            };
            if (keywords.length === 1 && keywords[0] === "") {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x" /></div>'
            } else if (resultItems.length === 0) {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-frown-o fa-5x" /></div>'
            } else {
              resultItems.sort(function (resultLeft, resultRight) {
                if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
                  return resultRight.searchTextCount - resultLeft.searchTextCount;
                } else if (resultLeft.hitCount !== resultRight.hitCount) {
                  return resultRight.hitCount - resultLeft.hitCount;
                } else {
                  return resultRight.id - resultLeft.id;
                }
              });
              var searchResultList = '<ul class=\"search-result-list\">';
              resultItems.forEach(function (result) {
                searchResultList += result.item;
              })
              searchResultList += "</ul>";
              resultContent.innerHTML = searchResultList;
            }
          }

          if ('auto' === 'auto') {
            input.addEventListener('input', inputEventFunction);
          } else {
            $('.search-icon').click(inputEventFunction);
            input.addEventListener('keypress', function (event) {
              if (event.keyCode === 13) {
                inputEventFunction();
              }
            });
          }

          // remove loading animation
          $(".local-search-pop-overlay").remove();
          $('body').css('overflow', '');

          proceedsearch();
        }
      });
    }

    // handle and trigger popup window;
    $('.popup-trigger').click(function(e) {
      e.stopPropagation();
      if (isfetched === false) {
        searchFunc(path, 'local-search-input', 'local-search-result');
      } else {
        proceedsearch();
      };
    });

    $('.popup-btn-close').click(onPopupClose);
    $('.popup').click(function(e){
      e.stopPropagation();
    });
    $(document).on('keyup', function (event) {
      var shouldDismissSearchPopup = event.which === 27 &&
        $('.search-popup').is(':visible');
      if (shouldDismissSearchPopup) {
        onPopupClose();
      }
    });
  </script>





  

  
  <script src="https://cdn1.lncld.net/static/js/av-core-mini-0.6.1.js"></script>
  <script>AV.initialize("A9MeTkIR49W9kOOSvkggsXge-gzGzoHsz", "HaIEOSDyxpcjwOjRjCYxiWjA");</script>
  <script>
    function showTime(Counter) {
      var query = new AV.Query(Counter);
      var entries = [];
      var $visitors = $(".leancloud_visitors");

      $visitors.each(function () {
        entries.push( $(this).attr("id").trim() );
      });

      query.containedIn('url', entries);
      query.find()
        .done(function (results) {
          var COUNT_CONTAINER_REF = '.leancloud-visitors-count';

          if (results.length === 0) {
            $visitors.find(COUNT_CONTAINER_REF).text(0);
            return;
          }

          for (var i = 0; i < results.length; i++) {
            var item = results[i];
            var url = item.get('url');
            var time = item.get('time');
            var element = document.getElementById(url);

            $(element).find(COUNT_CONTAINER_REF).text(time);
          }
          for(var i = 0; i < entries.length; i++) {
            var url = entries[i];
            var element = document.getElementById(url);
            var countSpan = $(element).find(COUNT_CONTAINER_REF);
            if( countSpan.text() == '') {
              countSpan.text(0);
            }
          }
        })
        .fail(function (object, error) {
          console.log("Error: " + error.code + " " + error.message);
        });
    }

    function addCount(Counter) {
      var $visitors = $(".leancloud_visitors");
      var url = $visitors.attr('id').trim();
      var title = $visitors.attr('data-flag-title').trim();
      var query = new AV.Query(Counter);

      query.equalTo("url", url);
      query.find({
        success: function(results) {
          if (results.length > 0) {
            var counter = results[0];
            counter.fetchWhenSave(true);
            counter.increment("time");
            counter.save(null, {
              success: function(counter) {
                var $element = $(document.getElementById(url));
                $element.find('.leancloud-visitors-count').text(counter.get('time'));
              },
              error: function(counter, error) {
                console.log('Failed to save Visitor num, with error message: ' + error.message);
              }
            });
          } else {
            var newcounter = new Counter();
            /* Set ACL */
            var acl = new AV.ACL();
            acl.setPublicReadAccess(true);
            acl.setPublicWriteAccess(true);
            newcounter.setACL(acl);
            /* End Set ACL */
            newcounter.set("title", title);
            newcounter.set("url", url);
            newcounter.set("time", 1);
            newcounter.save(null, {
              success: function(newcounter) {
                var $element = $(document.getElementById(url));
                $element.find('.leancloud-visitors-count').text(newcounter.get('time'));
              },
              error: function(newcounter, error) {
                console.log('Failed to create');
              }
            });
          }
        },
        error: function(error) {
          console.log('Error:' + error.code + " " + error.message);
        }
      });
    }

    $(function() {
      var Counter = AV.Object.extend("Counter");
      if ($('.leancloud_visitors').length == 1) {
        addCount(Counter);
      } else if ($('.post-title-link').length > 1) {
        showTime(Counter);
      }
    });
  </script>



  
<script>
(function(){
    var bp = document.createElement('script');
    var curProtocol = window.location.protocol.split(':')[0];
    if (curProtocol === 'https') {
        bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';        
    }
    else {
        bp.src = 'http://push.zhanzhang.baidu.com/push.js';
    }
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(bp, s);
})();
</script>


  

  

  

</body>
</html>
